package edu.pucsp.predatorprey.simulador.java;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import edu.pucsp.predatorprey.simulador.api.SaidaSimulacao;
import edu.pucsp.predatorprey.simulador.api.Simulador;
import edu.pucsp.predatorprey.simulador.impl.Estatistico;
import edu.pucsp.predatorprey.simulador.java.criatura.AbstractCriatura;
import edu.pucsp.predatorprey.simulador.java.posicao.impl.Posicao;
import edu.pucsp.predatorprey.simulador.java.terreno.api.ObservadorTerreno;
import edu.pucsp.predatorprey.simulador.java.terreno.api.Terreno;

public class SimuladorJava implements Simulador, ObservadorTerreno, Runnable {

    private final Set<AbstractCriatura> criaturas = Collections.newSetFromMap(new ConcurrentHashMap<AbstractCriatura, Boolean>());

    private final int numThreads;

    private final Terreno terreno;

    private final ThreadPoolExecutor executor;

    private Thread thread;

    private Estatistico estatistico;

    private boolean executando = false;

    private SaidaSimulacao saida;

    public SimuladorJava(Terreno terreno) {
        this.terreno = terreno;
        for (AbstractCriatura criatura : terreno.obterPosicaoCriaturas().keySet()) {
            this.criaturas.add(criatura);
        }
        this.numThreads = this.calcularNumeroThreads();
        this.executor =
                new ThreadPoolExecutor(this.numThreads, this.numThreads, 0, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<Runnable>());
    }

    @Override
    public void definirSaida(SaidaSimulacao saida) {
        this.saida = saida;
    }

    @Override
    public void iniciarSimulacao() {
        this.estatistico = new Estatistico(this.criaturas);
        this.executando = true;

        this.thread = new Thread(this);
        this.thread.start();

        this.saida.numeroExecucoesSimultaneas(this.numThreads);
        this.saida.simulacaoIniciou(this.terreno.obterPosicaoCriaturas());
    }

    @Override
    public void pararSimulacao() {
        this.executando = false;
    }

    @Override
    public void criaturaMorreu(AbstractCriatura criatura, Posicao posicao) {
        this.criaturas.remove(criatura);
        this.estatistico.registrarMorte(criatura);
        this.saida.criaturaMorreu(criatura, posicao);
    }

    @Override
    public void criaturaNasceu(AbstractCriatura criatura, Posicao posicao) {
        this.criaturas.add(criatura);
        this.estatistico.registrarNascimento(criatura);
        this.saida.criaturaNasceu(criatura, posicao);
    }

    @Override
    public void criaturaMoveu(Posicao origem, Posicao destino) {
        this.saida.criaturaMoveu(origem, destino);
    }

    @Override
    public void run() {
        try {
            while (this.executando && this.criaturas.size() > 0 && this.estatistico.getCiclo() <= 10000) {
                for (AbstractCriatura criatura : this.criaturas) {
                    this.executor.execute(criatura);
                }
                while (this.executando && this.executor.getQueue().size() > 0) {
                    this.executor.awaitTermination(1, TimeUnit.MILLISECONDS);
                }
                if (this.executando) {
                    this.saida.cicloCompleto(this.estatistico.cicloCompleto());
                }
            }
            this.executor.shutdown();
            this.saida.simulacaoParou();
        } catch (InterruptedException e) {
        }
    }

    private int calcularNumeroThreads() {
        return Runtime.getRuntime().availableProcessors() + 1;
    }

}
